package util;

import static java.lang.String.format;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Aims to simplify read and verify command line arguments on startup.<br>
 * Usage: java [-options] &ltclass&gt [-option[ value] ...] [target ...]
 * <p>
 * Example:
 *
 * <pre>
 * public static void main(final String[] args) throws ParseException {
 *     <i>// definition of optional and mandatory arguments</i>
 *     CmdStartupHelper sh = new CmdStartupHelper();
 *     sh.option(&quot;date&quot;, &quot;[yyyy-MM-dd] date to query updates from.&quot;);
 *     sh.target(&quot;description of first mandatory argument&quot;);
 *     sh.target(&quot;description of second mandatory argument&quot;);
 *     <i>// verify command line arguments to fit definition and prepare to use</i>
 *     sh.processArgs(args);
 *     
 *     <i>// option usage</i>
 *     Date date = null;
 *     if (sh.getOption(&quot;date&quot;) != null)
 *         date = new SimpleDateFormat(&quot;yyyy-MM-dd&quot;).parse(sh.getOption(&quot;date&quot;));
 *     System.out.println(&quot;date: &quot; + date);
 *     
 *     <i>// mandatory arguments usage</i>
 *     System.out.println(&quot;first mandatory argument: &quot; + sh.getTarget(0));
 *     System.out.println(&quot;second mandatory argument: &quot; + sh.getTarget(1));
 * }
 * </pre>
 *
 * Output (mandatory arguments were not specified):
 *
 * <pre>
 * Too few arguments.
 * Usage: java [-options] &ltclass&gt [-option[ value] ...] [target ...]
 * where class options include:
 *     -help                               print this help message
 *     -date                               [yyyy-MM-dd] date to query updates from.
 * where class required arguments include:
 *     0                               description of first mandatory argument
 *     1                               description of second mandatory argument
 * </pre>
 *
 * Inspired by Ant build tool.
 *
 * @author Mykhaylo Adamovych
 */
public class CmdStartupHelper {
    public static final String HELP_FORMAT = "\n\t%-30s \t%s";
    public static final String HELP_FORMAT_NEW_LINE = format(HELP_FORMAT, "", "\t");
    private final Map<String, String> options = new HashMap<String, String>();
    private final List<String> targets = new LinkedList<String>();
    private final List<String> targetsDescription = new LinkedList<String>();
    private final Map<String, String> optionsDescription = new LinkedHashMap<String, String>() {{
        put("help", "print this help message");
    }};
    
    /**
     * Optional parameter definition.
     *
     * @param name
     * @param description
     */
    public void option(final String name, final String description) {
        optionsDescription.put(name, description.replaceAll("\n", HELP_FORMAT_NEW_LINE));
    }
    
    /**
     * Mandatory parameter definition.
     *
     * @param description
     */
    public void target(final String description) {
        targetsDescription.add(description.replaceAll("\n", HELP_FORMAT_NEW_LINE));
    }
    
    /**
     * Returns specified optional command line argument using its name.
     *
     * @see #isDefined(String)
     */
    public String getOption(final String key) {
        return options.get(key);
    }
    
    /**
     * Checks whether the option is specified within command line arguments. To get optional argument use {@link #getOption(String)}.
     */
    public boolean isDefined(final String option) {
        return options.containsKey(option);
    }
    
    /**
     * Returns specified mandatory command line argument using its index.
     */
    public String getTarget(final int idx) {
        return targets.get(idx);
    }
    
    /**
     * Returns all defined mandatory command line arguments.
     */
    public List<String> getTargets() {
        return targets;
    }
    
    private void printAndExit(final String msg) {
        final StringBuilder sb = new StringBuilder();
        if (msg != null)
            sb.append(msg + "\n");
        sb.append("Usage: java [-options] <class> [-option[ value] ...] [target ...]");
        sb.append("\nwhere class options include:");
        for (final Entry<String, String> entry : optionsDescription.entrySet())
            sb.append(format(HELP_FORMAT, "-" + entry.getKey(), entry.getValue()));
        sb.append("\nwhere class required arguments include:");
        for (int i = 0; i < targetsDescription.size(); i++)
            sb.append(format(HELP_FORMAT, i, targetsDescription.get(i)));
        System.out.println(sb.toString());
        System.exit(0);
    }
    
    /**
     * Verify command line arguments to fit definition and prepare to use.
     */
    public void processArgs(final String... args) {
        String processingOption = null;
        boolean isProcessingOptions = true;
        for (final String arg : args) {
            if (isProcessingOptions && arg.startsWith("-")) {
                final String option = arg.replaceFirst("\\A-", "");
                if (!optionsDescription.containsKey(option))
                    printAndExit("Unrecognized option: -" + option);
                if ("help".equals(option))
                    printAndExit(null);
                processingOption = option;
                continue;
            }
            if (processingOption != null)
                options.put(processingOption, arg);
            else {
                targets.add(arg);
                isProcessingOptions = false;
            }
            processingOption = null;
        }
        if (targets.size() < targetsDescription.size())
            printAndExit("Too few arguments.");
    }
}